﻿
/*******************************************************************************************
// ©2009 Microsoft Corporation.  This code is provided under the Microsoft Public License.
*******************************************************************************************/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Rtc.Collaboration;
using System.Diagnostics;
using System.Configuration;

namespace ClientBilling
{
    public static class BilledCallTracker
    {
        #region Instance Variables
        private static Dictionary<string, BilledCallRecord> _billedCallRecords = new Dictionary<string, BilledCallRecord>();
        #endregion

        #region Static Constructor
        static BilledCallTracker()
        {
            // You can uncomment the line below to add some test data to the billed call tracker.
            //
            // AddDummyData();
        }

        private static void AddDummyData()
        {
            Case primaryCase = CrmSystem.GetClients()[0].Cases[0];

            List<CaseAllocation> allocations = BuildInitialCaseAllocations(primaryCase);

            _billedCallRecords.Add("abc",
                new BilledCallRecord(primaryCase.Client, DateTime.Now)
                {
                    ConversationId = "abc",
                    CaseAllocations = allocations
                }
            );

            _billedCallRecords.Add("123",
                new BilledCallRecord(primaryCase.Client, DateTime.Now)
                {
                    ConversationId = "123",
                    CaseAllocations = allocations,
                    End = DateTime.Now.AddMinutes(25)
                }
            );
        }
        #endregion

        /// <summary>
        /// Begin tracking the duration of the call, associate it with the client,
        /// and subscribe to call events so we know when the call ends or someone leaves.
        /// </summary>
        public static void AttachCaseToCall(Case attachedCase, Call call)
        {
            SubscribeToCallTermination(call);
            AttachCaseToConversation(attachedCase, call.Conversation);
        }

        #region Handle Call State Changes
        private static void SubscribeToCallTermination(Call call)
        {
            call.StateChanged += new EventHandler<CallStateChangedEventArgs>(CallStateChangedHandler);
        }

        /// <summary>
        /// If the call state changes, check whether the call has been terminated and
        /// if so, stop billing for the call.
        /// </summary>
        private static void CallStateChangedHandler(object sender, CallStateChangedEventArgs e)
        {
            if (e.State == CallState.Terminated)
            {
                Call call = sender as Call;
                if (call != null)
                {
                    BilledCallTracker.StopBillingConversation(call.Conversation);
                }
            }
        }
        #endregion

        #region Create Billing Record For Call

        /// <summary>
        /// Associate a particular client case with the provided conversation.
        /// </summary>
        /// <param name="attachedCase">The case that will be attached.</param>
        /// <param name="conversation">The conversation to which the case should be attached.</param>
        public static void AttachCaseToConversation(Case attachedCase, Conversation conversation)
        {
            Debug.Assert(!IsTrackedConversation(conversation.Id), "!IsTrackedConversation(conversation.Id)");

            // First, we create a billing record for this conversation.
            CreateBilledCallRecord(attachedCase, conversation);

            // Watch for participant changes so that we can stop billing the call if someone leaves.
            SubscribeToConversationParticipantChanges(conversation);
        }

        /// <summary>
        /// Create a new record with the billing information for this conversation.
        /// </summary>
        /// <param name="primaryCase">The case to which the call will be initially allocated at 100%.</param>
        /// <param name="conv">The UCMA conversation associated with this billed call.</param>
        private static void CreateBilledCallRecord(Case primaryCase, Conversation conv)
        {
            List<CaseAllocation> allocations = BuildInitialCaseAllocations(primaryCase);

            _billedCallRecords.Add(conv.Id,
                new BilledCallRecord(primaryCase.Client, DateTime.Now)
                {
                    ConversationId = conv.Id,
                    CaseAllocations = allocations
                }
            );
        }

        /// <summary>
        /// Build the initial array of case allocations for this call.
        /// </summary>
        /// <param name="primaryCase"></param>
        /// <returns></returns>
        private static List<CaseAllocation> BuildInitialCaseAllocations(Case primaryCase)
        {
            Client callClient = primaryCase.Client;

            List<CaseAllocation> allocations = new List<CaseAllocation>();

            foreach (Case c in callClient.Cases)
            {
                allocations.Add(new CaseAllocation(c));
            }

            // Set the allocation of the primary case to 100%.
            allocations.Find(allocation => allocation.Case == primaryCase).PortionOfCall = 1;

            return allocations;
        }

        #endregion

        #region Handle Conversation Participant Changes

        private static void SubscribeToConversationParticipantChanges(Conversation conv)
        {
            conv.RemoteParticipantAttendanceChanged += 
                new EventHandler<ParticipantAttendanceChangedEventArgs>(RemoteParticipantAttendanceChangedHandler);
        }

        /// <summary>
        /// If the lawyer or the client leaves the call, stop billing.
        /// </summary>
        private static void RemoteParticipantAttendanceChangedHandler(object sender, ParticipantAttendanceChangedEventArgs e)
        {
            Debug.Assert(sender is Conversation, "sender is Conversation");

            Conversation conv = sender as Conversation;

            Client callClient = BilledCallTracker.GetClientForBilledConversationId(conv.Id);

            Debug.Assert(callClient != null, "callClient != null");

            // Make sure the participant who left the call is either the client or
            // the lawyer. If it's a third party that was brought into the call later,
            // their departure shouldn't end the billing.
            bool clientOrLawyerLeftCall =
                e.Removed.Any(participant => participant.Uri == ConfigUtility.LawyerSipUri) ||
                e.Removed.Any(participant => participant.Uri == callClient.SipUri);

            if (clientOrLawyerLeftCall)
            {
                BilledCallTracker.StopBillingConversation(conv);
            }
        }

        #endregion

        #region Handle Case Allocations

        /// <summary>
        /// Sets the case allocations for a billed conversation.
        /// </summary>
        public static void SetAllocationsForConversation(string conversationId, List<CaseAllocation> allocations)
        {
            Debug.Assert(!string.IsNullOrEmpty(conversationId), "!string.IsNullOrEmpty(conversationId)");
            Debug.Assert(IsTrackedConversation(conversationId), "IsTrackedConversation(conversationId)");

            MakeAllocationsSumToOne(allocations);

            _billedCallRecords[conversationId].CaseAllocations = allocations;
        }

        /// <summary>
        /// Retrieves the case allocations for a billed conversation.
        /// </summary>
        /// <param name="conversationId"></param>
        /// <returns></returns>
        public static List<CaseAllocation> GetAllocationsForConversation(string conversationId)
        {
            Debug.Assert(!string.IsNullOrEmpty(conversationId), "!string.IsNullOrEmpty(conversationId)");
            Debug.Assert(IsTrackedConversation(conversationId), "IsTrackedConversation(conversationId)");

            List<CaseAllocation> allocations = _billedCallRecords[conversationId].CaseAllocations;

            return allocations.ToList();
        }

        private static void MakeAllocationsSumToOne(List<CaseAllocation> allocations)
        {
            decimal sum = 0;

            // Get the total of all of the allocations.
            foreach (CaseAllocation allocation in allocations)
            {
                sum += allocation.PortionOfCall;
            }

            // Calculate the difference between the total and one.
            decimal differenceFromOne = 1 - sum;

            // Alter the last allocation so that the total equals one.
            allocations[allocations.Count - 1].PortionOfCall += differenceFromOne;
        }

        #endregion

        #region Get Call Record Information

        /// <summary>
        /// Retrieves all the records of billed calls.
        /// </summary>
        /// <returns></returns>
        public static List<BilledCallRecord> GetCallRecords()
        {
            List<BilledCallRecord> records = new List<BilledCallRecord>();

            foreach (KeyValuePair<string, BilledCallRecord> pair in _billedCallRecords)
            {
                records.Add(pair.Value);
            }

            return records;
        }

        /// <summary>
        /// Returns the client associated with the conversation whose ID is provided.
        /// </summary>
        /// <param name="conversationId">The ID of the conversation.</param>
        /// <returns>Client</returns>
        public static Client GetClientForBilledConversationId(string conversationId)
        {
            Debug.Assert(!string.IsNullOrEmpty(conversationId), "!string.IsNullOrEmpty(conversationId)");
            Debug.Assert(IsTrackedConversation(conversationId), "IsTrackedConversation(conversationId)");

            return _billedCallRecords[conversationId].Client;
        }

        #endregion

        /// <summary>
        /// Stamp the billing record for this conversation with the current time as an end time.
        /// </summary>
        private static void StopBillingConversation(Conversation conv)
        {
            Debug.Assert(_billedCallRecords.ContainsKey(conv.Id), "_billedCallRecords.ContainsKey(conv.Id)");

            // Update the end time of the conversation.
            // The total call length will display on the billing page.
            _billedCallRecords[conv.Id].End = DateTime.Now;
        }

        /// <summary>
        /// Returns true if there is a billed call record for the conversation with the provided ID.
        /// </summary>
        private static bool IsTrackedConversation(string convId)
        {
            return _billedCallRecords.ContainsKey(convId);
        }

        

        
    }
}
